﻿namespace Cloudio.Web.UI.Editor
{
    using System;
    using System.IO;
    using System.Web.UI;
    using System.Web;
    using Cloudio.Core.WebFormsParser;
    using ToolboxItem;
    using ASP = System.Web.UI.WebControls;
    using Gaia.WebWidgets;
    using HtmlAgilityPack;
    using ControlExtensions;

    public class EditorBase: Panel
    {
        private const string PanelId = "pnlEditorBase";
        public event EventHandler<SmartTagEventArgs> SmartTagClick;

        protected override void OnInit(EventArgs e)
        {
            ID = PanelId;
            base.OnInit(e);
        }

        protected override void OnLoad(EventArgs e)
        {
            InitializeComponent();
            base.OnLoad(e);
        }

        private void InitializeComponent()
        {
            Attributes.Add("contenteditable", "true");
            Aspects.Add(GetDroppable());
            AddInitialMarkup();
        }

        public static string SourceMapPath
        {
            get
            {
                return HttpContext.Current.Server.MapPath("~/files/SimpleMarkup.aspx");
            }
        }

        public string Markup
        {
            get { return LoadFileContent(SourceMapPath); }
        }

        private string GetNewId()
        {
            var highestIndex = 0;
            foreach (var control in Controls.All())
            {
                if (control != null && !string.IsNullOrEmpty(control.ID) && control.ID.StartsWith("id"))
                    if (int.Parse(control.ID.Substring(2)) > highestIndex)
                        highestIndex = int.Parse(control.ID.Substring(2));
            }

            return "id" + (highestIndex + 1);
        }

        public void RefreshContent()
        {
            Controls.Clear();
            AddInitialMarkup();
        }

        private void AddInitialMarkup()
        {
            var htmlDocument = new HtmlDocument();
            htmlDocument.LoadHtml(Markup);

            TraverseMarkup(this, htmlDocument.DocumentNode.SelectNodes("//body/*[@runat]"));
        }


        private void TraverseMarkup(Control parentControl, HtmlNodeCollection htmlNodeCollection)
        {
            if (htmlNodeCollection == null) return;

            foreach (var selectNode in htmlNodeCollection)
            {
                var nodeName = selectNode.Name.ToLower();

                //only support gaia controls so far
                if (!nodeName.Contains("gaia")) continue;

                //parse type from gaia:Button to Button
                var typeName = nodeName.Substring(nodeName.IndexOf(":") + 1);
                typeName = "Gaia.WebWidgets." + typeName;

                //find id of control
                var id = "";
                if (selectNode.Attributes["id"] != null)
                    id = selectNode.Attributes["id"].Value;

                //Add control to given parent panel
                var controlAdded = AddControlToParentContainer(parentControl, typeName, id);

                //TODO should we continue or throw?
                if (controlAdded == null) continue;

                var isContainerControl = controlAdded.GetType() == typeof (Panel);

                //traverse recursive markup with panels support
                if (isContainerControl && selectNode.HasChildNodes)
                    TraverseMarkup(controlAdded, selectNode.ChildNodes);
            }
        }

        private AspectDroppable GetDroppable()
        {
            var drop = new AspectDroppable { HoverClass = "hoverPanel" };
            drop.Dropped += dropEditor_Dropped;
            return drop;
        }

        private void dropEditor_Dropped(object sender, AspectDroppable.DroppedEventArgs e)
        {
            //if not from toolbox, ignore
            if (string.IsNullOrEmpty(e.IdToPass)) return;

            //find where it was dropped and add it to the markup
            //get the origin of the aspect
            var aspect = (IAspect)sender;
            var droppedOnContainer = (Panel)aspect.ParentControl;

            //here we have found the ID of the control it was dropped on
            //now we should find this ID in the markup, and add it inside as a child control
            var addedControl = AddControlToParentContainer(droppedOnContainer, e.IdToPass);

            //save dropped item to markup
            //TODO we need to find the element to drop inside
            //var node = _htmlDocument.DocumentNode.AppendChild();

            //here we create the markup to store in our aspx file
            var htmlNodeToAdd = HtmlNode.CreateNode(DesignSerializer.Serialize(addedControl));

            //if nothing to add
            if (htmlNodeToAdd == null) return;

            //standard root
            var htmlDocument = new HtmlDocument();
            htmlDocument.LoadHtml(LoadFileContent(SourceMapPath));
            var foundHtmlNode = htmlDocument.DocumentNode.SelectSingleNode("//body");

            // if it is the root of the editor, add it as a children at the bottom
            if (droppedOnContainer.ID.ToLower() == PanelId.ToLower())
                foundHtmlNode.AppendChild(htmlNodeToAdd);
            //if we didn't drop on the root, we need to traverse to find where we will add it as a children
            else
            {
                if (foundHtmlNode.HasChildNodes)
                    FindChildrenHtmlNodeById(droppedOnContainer.ID, foundHtmlNode.ChildNodes, ref foundHtmlNode);

                foundHtmlNode.AppendChild(htmlNodeToAdd);
            }

            //if it is a root, we need to put inside form
            if (foundHtmlNode == null)
                throw new Exception("Didn't expect emtpy");

            //save file
            SaveToFile(SourceMapPath, htmlDocument.DocumentNode.OuterHtml);
        }

        private Control AddControlToParentContainer(Control container, string controlTypeFullName)
        {
            return AddControlToParentContainer(container, controlTypeFullName, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="container"></param>
        /// <param name="controlTypeFullName"></param>
        /// <param name="id"></param>
        /// <returns>The control added</returns>
        private Control AddControlToParentContainer(Control container, string controlTypeFullName, string id)
        {
            var addedControl = (ASP.WebControl)GaiaToolboxItem.GetGaiaControlForType(controlTypeFullName);

            //if not anything found
            if (addedControl == null) return null;

            //set id
            addedControl.ID = string.IsNullOrEmpty(id) ? GetNewId() : id;

            //add content and droppable if a Gaia Panel
            if (addedControl.GetType() == typeof(Panel))
            {
                var panel = (Panel)addedControl;
                AddDummyContentToPanel(panel);
                panel.Aspects.Add(GetDroppable());
                panel.Style["margin-left"] = "5px";
                panel.Style["background-color"] = "yellow";
                container.Controls.Add(panel);
            }
            else
                container.Controls.Add(addedControl);

            //add a smart tag after the control itself
            var smartTagButton = GetSmartTagButton(controlTypeFullName);

            if (smartTagButton != null) 
                container.Controls.Add(smartTagButton);

            return addedControl;
        }

        private SmartTagButton GetSmartTagButton(string controlTypeFullName)
        {
            var smartTagButton = new SmartTagButton(controlTypeFullName)
                                     {
                                         CssClass = "smartTag",
                                         ImageUrl = "~/media/icons/smart_right.gif",
                                         AlternateText = controlTypeFullName
                                     };

            smartTagButton.Click += delegate(object sender, ImageClickEventArgs e)
                                        {
                                            if (SmartTagClick != null)
                                                SmartTagClick(sender,
                                                              new SmartTagEventArgs(
                                                                  ((SmartTagButton) sender).ControlTypeFullName));
                                        };
            return smartTagButton;
        }

        private static void AddDummyContentToPanel(Control panel)
        {
            var lit = new LiteralControl("Dummy content");
            panel.Controls.Add(lit);
        }

        /// <summary>
        /// Find the the HtmlNode recursively in the children
        /// </summary>
        /// <param name="id">ID of control to find</param>
        /// <param name="htmlNodes">HtmlNodeCollection</param>
        /// <param name="nodeFound"></param>
        /// <returns>HtmlNode</returns>
        private static void FindChildrenHtmlNodeById(string id, HtmlNodeCollection htmlNodes, ref HtmlNode nodeFound)
        {
            foreach (var childNode in htmlNodes)
            {
                if (childNode.NodeType != HtmlNodeType.Element) continue;

                foreach (var attribute in childNode.Attributes)
                {
                    if (attribute.Name.ToLower() == "id" && attribute.Value.ToLower() == id.ToLower())
                    {
                        nodeFound = childNode;
                        return;
                    }

                }

                //recursive find it
                if (childNode.HasChildNodes)
                    FindChildrenHtmlNodeById(id, childNode.ChildNodes, ref nodeFound);
            }

            return;
        }

        public void Reset()
        {
            SaveToFile(LoadFileContent(SourceMapPath));
        }

        public static void SaveToFile(string content)
        {
            SaveToFile(SourceMapPath, content);
        }

        private static void SaveToFile(string mapPathToSave, string content)
        {
            using (var sw = new StreamWriter(mapPathToSave))
            {
                // write a line of text to the file
                sw.Write(content);
                sw.Close();
            }
        }

        private static string LoadFileContent(string mapPath)
        {
            string result;
            {
                using (var file = File.OpenRead(mapPath))
                {
                    using (var reader = new StreamReader(file, true))
                    {
                        result = reader.ReadToEnd();
                        reader.Close();
                    }
                    file.Close();
                }
            }
            return result;
        }
    }
}
